#include <glib.h>
#include <gtk/gtk.h>
#include <SDL.h>
#include "racanhack.h"
#include "task.h"
#include "video.h"
#include "cave.h"
#include "player.h"
#include "monster.h"
#include "monster2.h"
#include "object.h"
#include "object2.h"
#include "stair.h"
#include "floor.h"
#include "los.h"
#include "mapchips.h"
#include "invent.h"
#include "game.h"
#include "main.h"

static int mhp_table[PLAYER_LEV_MAX];
static int atk_table[PLAYER_LEV_MAX];

int motion_end(struct _task *task)
{
  if (main_player_running == TRUE) return TRUE;
  if (task->count >= MOTION) return TRUE;
  return FALSE;
}

direction ddd(int i)
{
  switch (i) {
  case 0: return DIR_2;
  case 1: return DIR_8;
  case 2: return DIR_6;
  case 3: return DIR_4;
  case 4: return DIR_3;
  case 5: return DIR_1;
  case 6: return DIR_9;
  case 7: return DIR_7;
  };
  g_assert_not_reached();
  return DIR_NULL;
}

int ddx(direction dir)
{
  switch (dir) {
  case DIR_1: return -1;
  case DIR_2: return 0;
  case DIR_3: return 1;
  case DIR_4: return -1;
    /* case DIR_NULL: */
  case DIR_6: return 1;
  case DIR_7: return -1;
  case DIR_8: return 0;
  case DIR_9: return 1;
  };
  g_assert_not_reached();
  return 0;
}

int ddy(direction dir)
{
  switch (dir) {
  case DIR_1: return 1;
  case DIR_2: return 1;
  case DIR_3: return 1;
  case DIR_4: return 0;
    /* case DIR_NULL: */
  case DIR_6: return 0;
  case DIR_7: return -1;
  case DIR_8: return -1;
  case DIR_9: return -1;
  };
  g_assert_not_reached();
  return 0;
}

direction lefthand(int count, direction dir)
{
  count = count % 4;
  switch (dir) {
  case DIR_1: 
    dir = DIR_2;
    break;
  case DIR_3:
    dir = DIR_6;
    break;
  case DIR_9:
    dir = DIR_8;
    break;
  case DIR_7:
    dir = DIR_4;
    break;
  };
  switch(dir) {
  case DIR_8:
    switch(count) {
    case 0: return DIR_6;
    case 1: return DIR_8;
    case 2: return DIR_4;
    case 3: return DIR_2;
    };
    break;
  case DIR_4:
    switch(count) {
    case 0: return DIR_8;
    case 1: return DIR_4;
    case 2: return DIR_2;
    case 3: return DIR_6;
    };
    break;
  case DIR_2:
    switch(count) {
    case 0: return DIR_4;
    case 1: return DIR_2;
    case 2: return DIR_6;
    case 3: return DIR_8;
    };
    break;
  case DIR_6:
    switch(count) {
    case 0: return DIR_2;
    case 1: return DIR_6;
    case 2: return DIR_8;
    case 3: return DIR_4;
    };
    break;
  };
  g_assert_not_reached();
  return DIR_NULL;
}

direction opposite(direction dir)
{
  switch (dir) {
  case DIR_1: return DIR_9;
  case DIR_2: return DIR_8;
  case DIR_3: return DIR_7;
  case DIR_4: return DIR_6;
    /* case DIR_NULL: */
  case DIR_6: return DIR_4;
  case DIR_7: return DIR_3;
  case DIR_8: return DIR_2;
  case DIR_9: return DIR_1;
  };
  g_assert_not_reached();
  return DIR_NULL;
}

struct _coord coord_dd(struct _coord cur_coord, direction dir)
{
  struct _coord dest_coord;
  dest_coord.x = cur_coord.x + ddx(dir);
  dest_coord.y = cur_coord.y + ddy(dir);
  g_assert((dest_coord.x >= 0) ||
	   (dest_coord.y >= 0) ||
	   (dest_coord.x < MAP_W) ||
	   (dest_coord.y < MAP_H));
  return dest_coord;
}

gboolean is_passable(struct _coord coord, direction dir)
{
  struct _coord c;
  c = coord_dd(coord, dir);
  if (is_map_wall(c) || is_player(c) || is_monster(c)) return FALSE;
  switch (dir) {
  case DIR_1:
    if (is_map_wall(coord_dd(coord, DIR_4))) return FALSE;
    if (is_map_wall(coord_dd(coord, DIR_2))) return FALSE;
    break;
  case DIR_3:
    if (is_map_wall(coord_dd(coord, DIR_2))) return FALSE;
    if (is_map_wall(coord_dd(coord, DIR_6))) return FALSE;
    break;
  case DIR_7:
    if (is_map_wall(coord_dd(coord, DIR_4))) return FALSE;
    if (is_map_wall(coord_dd(coord, DIR_8))) return FALSE;
    break;
  case DIR_9:
    if (is_map_wall(coord_dd(coord, DIR_6))) return FALSE;
    if (is_map_wall(coord_dd(coord, DIR_8))) return FALSE;
    break;
  };
  return TRUE;
};

int distance(struct _coord c1, struct _coord c2)
{
  struct _coord ac;
  ac.x = (c1.x > c2.x) ? (c1.x - c2.x) : (c2.x - c1.x);
  ac.y = (c1.y > c2.y) ? (c1.y - c2.y) : (c2.y - c1.y);
  return ((ac.y > ac.x) ? (ac.y + (ac.x>>1)) : (ac.x + (ac.y>>1)));
}

void disturb()
{
  main_player_running = FALSE;
}

int animecount(struct _task *task)
{
  return (task->count / 2) % ANIME_J;
}

void take_hit(struct _player *player, guint damage, const gchar *format, ...)
{
  player->damage = CLAMP(player->damage + damage, 0, G_MAXUINT);
  if (player->chp - player->damage <= 0) {
    va_list args;
    va_start(args, format);
    g_vsnprintf(player->died_from, 32, format, args);
    va_end(args);
  };
}

gboolean check_lev_up(struct _player *player)
{
  if (player->lev < PLAYER_LEV_MAX - 1) {
    if (player->exp >= lev_exp_table[player->lev]) {
      return TRUE;
    };
  };
  return FALSE;
}

void player_trans_state(state (*fp)(struct _task *task), struct _player *player)
{
  if (player->task != NULL) player->task->alive = FALSE;
  player->task = task_add(fp, player);
}

struct _player *is_player(struct _coord coord)
{
  if ((Player.coord.x == coord.x) && (Player.coord.y == coord.y)) return &Player;
  return NULL;
}

void player_init()
{
  SDL_Surface *s;
  SDL_Rect r;
  int i, j;
  r.w = SPRITE_W; r.h = SPRITE_H;
  s = video_bmp_load("bmp/player_idle.bmp");
  for (i = 0; i < 8; i++) {
    for (j = 0; j < ANIME_J; j++) {
      r.x = SPRITE_W * j; r.y = SPRITE_H * i;
      surface_player_idle[ddd(i)][j] = video_crop(s, &r);
    };
  };
  SDL_FreeSurface(s);
  s = video_bmp_load("bmp/player_attack.bmp");
  for (i = 0; i < 8; i++) {
    for (j = 0; j < ANIME_J; j++) {
      r.x = SPRITE_W * j; r.y = SPRITE_H * i;
      surface_player_attack[ddd(i)][j] = video_crop(s, &r);
    };
  };
  SDL_FreeSurface(s);
  s = video_bmp_load("bmp/player_damage.bmp");
  for (i = 0; i < 8; i++) {
    for (j = 0; j < ANIME_J; j++) {
      r.x = SPRITE_W * j; r.y = SPRITE_H * i;
      surface_player_damage[ddd(i)][j] = video_crop(s, &r);
    };
  };
  SDL_FreeSurface(s);
  lev_exp_table[1] = 10;
  lev_exp_table[2] = 30;
  lev_exp_table[3] = 60;
  lev_exp_table[4] = 100;
  lev_exp_table[5] = 150;
  lev_exp_table[6] = 230;
  lev_exp_table[7] = 350;
  lev_exp_table[8] = 500;
  lev_exp_table[9] = 700;
  lev_exp_table[10] = 950;
  lev_exp_table[11] = 1200;
  lev_exp_table[12] = 1500;
  lev_exp_table[13] = 1800;
  lev_exp_table[14] = 2300;
  lev_exp_table[15] = 3000;
  lev_exp_table[16] = 4000;
  atk_table[1] = 5;
  atk_table[2] = 7;
  atk_table[3] = 9;
  atk_table[4] = 11;
  atk_table[5] = 13;
  atk_table[6] = 16;
  atk_table[7] = 19;
  atk_table[8] = 22;
  atk_table[9] = 25;
  atk_table[10] = 29;
  atk_table[12] = 37;
  atk_table[13] = 41;
  atk_table[14] = 46;
  atk_table[15] = 51;
  atk_table[16] = 56;
  mhp_table[1] = 11;
  mhp_table[2] = 15;
  mhp_table[3] = 19;
  mhp_table[4] = 25;
  mhp_table[5] = 31;
  mhp_table[6] = 37;
  mhp_table[7] = 45;
  mhp_table[8] = 53;
  mhp_table[9] = 60;
  mhp_table[10] = 69;
  mhp_table[12] = 78;
  mhp_table[13] = 88;
  mhp_table[14] = 100;
  mhp_table[15] = 110;
  mhp_table[16] = 120;
}

void player_free()
{
  int i, j;
  for (i = 0; i < 8; i++) {
    for (j = 0; j < ANIME_J; j++) {
      SDL_FreeSurface(surface_player_idle[ddd(i)][j]);
      SDL_FreeSurface(surface_player_attack[ddd(i)][j]);
      SDL_FreeSurface(surface_player_damage[ddd(i)][j]);
    };
  };
  g_list_foreach(Player.inventory_list, (GFunc)my_free, NULL);
  g_list_free(Player.inventory_list);
}

struct _player *player_add()
{
  struct _player *player;
  player = &Player;
  player->coord = rand_coord();
  player->dir = DIR_2;
  player->status.type = STATUS_NORMAL;
  player->status.time = 0;
  player->exp = 0;
  player->lev = 1;
  player->mhp = mhp_table[player->lev];
  player->chp = player->mhp;
  player->mstr = 8;
  player->cstr = player->mstr;
  player->atk = atk_table[player->lev];
  player->weapon = noweapon_add();
  player->shield = noshield_add();
  player->arrow = noarrow_add();
  player->ring = noring_add();
  player->food = PLAYER_FOOD_MAX;
  player->food_count = 0;
  player->regen_count = 0;
  player->damage = 0;
  player->sprite = video_sprite_add(Z_ORDER_PLAYER);
  player->task = NULL;
  return player;
}

state player_turn_start(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->regen_count++;
  if (player->regen_count >= 8) {
    player->regen_count = 0;
    player->chp = CLAMP(player->chp + 1, 0, player->mhp);
  };
  player->food_count++;
  switch(player->ring->id) {
  case OBJECT_ID_SLOWDIGEST_RING:
    if (player->food_count >= 20) {
      player->food_count = 0;
      player->food = CLAMP(player->food - 1, 0, PLAYER_FOOD_MAX);
    };
    break;
  case OBJECT_ID_FASTDIGEST_RING:
    if (player->food_count >= 5) {
      player->food_count = 0;
      player->food = CLAMP(player->food - 1, 0, PLAYER_FOOD_MAX);
    };
    break;
  case OBJECT_ID_NORING:
    if (player->food_count >= 10) {
      player->food_count = 0;
      player->food = CLAMP(player->food - 1, 0, PLAYER_FOOD_MAX);
    };
    break;
  default:
    if (player->food_count >= 8) {
      player->food_count = 0;
      player->food = CLAMP(player->food - 1, 0, PLAYER_FOOD_MAX);
    };
    break;
  };
  if (player->food == 5) {
    msg("I want to eat something.");
  };
  if (player->food == 0) {
    msg("Hungry...I'm dying...");
    take_hit(player, 1, "damage from hunger");
  };
  if (player->status.type != STATUS_NORMAL) {
    player->status.time = CLAMP(player->status.time - 1, 0, G_MAXUINT);
    if ((player->status.type == STATUS_SLEEP) &&
	(player->ring->id == OBJECT_ID_SLEEPLESS_RING)) {
      msg("Your %s is flushing...!", object_name(player->ring));
      set_object_ident(player->ring, TRUE);
      player->status.time = 0;
    };
    if (player->status.time == 0) {
      switch (player->status.type) {
      case STATUS_POISON:
	msg("you restored poison naturally.");
	break;
      case STATUS_SLEEP:
	msg("you wake up.");
	break;
      case STATUS_CONFUSE:
	msg("you restored your confusion.");
	break;
      case STATUS_BLIND:
	msg("you restored your blindness.");
	break;
      default:
	g_assert_not_reached();
	break;
      };
      player->status.type = STATUS_NORMAL;
      player->status.time = 0;
    };
  };
  switch (player->status.type) {
  case STATUS_NORMAL:
    break;
  case STATUS_POISON:
    if (g_random_int_range(0, 4) == 0) {
      msg("you got damage from poison.");
      take_hit(player, 1, "damage from poison.");
    };
    break;
  case STATUS_SLEEP:
    msg("you are sleeping...");
    player_trans_state(player_action_end, player);
    return;
    break;
  case STATUS_CONFUSE:
    if (g_random_int_range(0, 4) == 0) {
      msg("you are confusing...");
      player->dir = ddd(g_random_int_range(0, 8));
      player_trans_state(player_move, player);
      return;
    };
    break;
  case STATUS_BLIND:
    msg("you are blind...");
    player_trans_state(player_action_end, player);
    return;
    break;
  default:
    g_assert_not_reached();
    break;
  };
  player_trans_state(player_action_start, player);
  return;
}


state player_action_start(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->x = player->coord.x * SPRITE_W;
  player->sprite->y = player->coord.y * SPRITE_H;
  player->sprite->surface = surface_player_idle[player->dir][animecount(task)];
  if ((keys[SDLK_h]) ||
      (keys[SDLK_k]) ||
      (keys[SDLK_j]) ||
      (keys[SDLK_l]) ||
      (keys[SDLK_y]) ||
      (keys[SDLK_b]) ||
      (keys[SDLK_n]) ||
      (keys[SDLK_u])) {
    if (keys[SDLK_h]) {
      player->dir = DIR_4;
    };
    if (keys[SDLK_k]) {
      player->dir = DIR_8;
    };
    if (keys[SDLK_j]) {
      player->dir = DIR_2;
    };
    if (keys[SDLK_l]) {
      player->dir = DIR_6;
    };
    if (keys[SDLK_y]) {
      player->dir = DIR_7;
    };
    if (keys[SDLK_b]) {
      player->dir = DIR_1;
    };
    if (keys[SDLK_n]) {
      player->dir = DIR_3;
    };
    if (keys[SDLK_u]) {
      player->dir = DIR_9;
    };
    if (keys[SDLK_s]) return;
    if ((keys[SDLK_LSHIFT]) ||
	(keys[SDLK_RSHIFT])) {
      main_player_running = TRUE;
      player_trans_state(player_move_running, player);
      return;
    } else {
      disturb();
      player_trans_state(player_move, player);
      return;
    };
  };
  if (keys[SDLK_SPACE]) {
    player_trans_state(player_action_end, player);
    return;
  };
  if (keys[SDLK_f]) {
    keys[SDLK_f] = FALSE;
    player_trans_state(player_attack, player);
    return;
  };
  if (keys[SDLK_d]) {
    keys[SDLK_d] = FALSE;
    player_trans_state(player_fire_arrow, player);
    return;
  };
  if (keys[SDLK_r]) {
    keys[SDLK_r] = FALSE;
    player_trans_state(player_pickup, player);
    return;
  };
  if (keys[SDLK_i]) {
    keys[SDLK_i] = FALSE;
    player_trans_state(player_open_inventory, player);
    return;
  };
  if (keys[SDLK_a]) {
    keys[SDLK_a] = FALSE;
    if ((keys[SDLK_LSHIFT]) ||
	  (keys[SDLK_RSHIFT])) {
      player_trans_state(player_up_stair, player);
      return;
    } else {
      player_trans_state(player_down_stair, player);
      return;
    };
  };
}

state player_action_end(struct _task *task)
{
  struct _player *player;
  struct _room *room;
  player = (struct _player *)task->pointer[0];
  if (player->damage > 0) {
    disturb();
    msg("you got %d damage.", player->damage);
    player->chp = CLAMP(player->chp - player->damage, 0, player->mhp);
    player->damage = 0;
    if (player->chp == 0) {
      player_trans_state(player_killed, player);
      return;
    };
  };
  if (player->food == 0) disturb();
  if (check_lev_up(player)) {
    player_trans_state(player_lev_up, player);
    return;
  };
  room = at_room(player->coord);
  if (room != NULL) {
    if (room->monsterhouse == TRUE) {
      GList *li;
      struct _monster *monster;
      msg("You entered to a Monster House!");
      room->monsterhouse = FALSE;
      for (li = g_list_first(curf->monster_list); li != NULL; li = g_list_next(li)) {
	monster = (struct _monster *)li->data;
	if (monster->status.type == STATUS_SLEEP_MONSTERHOUSE) {
	  monster->status.type = STATUS_NORMAL;
	  monster->status.time = 0;
	};	    
      };
    };
  };
  player_trans_state(player_turn_end, player);
  return;
}

state player_turn_end(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->surface = surface_player_idle[player->dir][animecount(task)];
}

state player_move(struct _task *task)
{
  struct _player *player;
  struct _object *object;
  player = (struct _player *)task->pointer[0];
  if (is_passable(player->coord, player->dir) == FALSE) {
    player_trans_state(player_action_start, player);
    return;
  };
  player->coord = coord_dd(player->coord, player->dir);
  object = is_object(player->coord);
  if (object != NULL) {
    msg("You found %s.", object_name(object));
  };
  task_add(move_player_anime, player);
  player_trans_state(player_action_end, player);
  return;
}

state move_player_anime(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->x += (SPRITE_W / MOTION) * ddx(player->dir);
  player->sprite->y += (SPRITE_H / MOTION) * ddy(player->dir);
  if (motion_end(task)) {
    task->alive = FALSE;
    return;
  };
}

state player_move_running(struct _task *task)
{
  struct _player *player;
  struct _object *object;
  player = (struct _player *)task->pointer[0];
  if (main_player_running == FALSE) {
    player_trans_state(player_action_start, player);
    return;
  };
  if (is_passable(player->coord, player->dir) == FALSE) {
    disturb();
    return;
  };
  player->coord = coord_dd(player->coord, player->dir);
  player->sprite->x = player->coord.x * SPRITE_W;
  player->sprite->y = player->coord.y * SPRITE_H;
  if (is_map_disturb(player->coord)) {
    disturb();
  };
  object = is_object(player->coord);
  if (object != NULL) {
    msg("You found %s.", object_name(object));
    disturb();
  } else if (is_stair(player->coord)) {
    disturb();
  } else {
    GList *li;
    struct _monster *monster;
    for (li = g_list_first(curf->monster_list); li != NULL; li = g_list_next(li)) {
      monster = (struct _monster *)li->data;
      if (is_los_onsight(monster->coord)) {
	disturb();
	break;
      };
    };
  };
  player_trans_state(player_action_end, player);
  return;
}

state player_attack(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->surface = surface_player_attack[player->dir][animecount(task)];
  if (motion_end(task)) {
    object_player_trans_state(object_player_wield, player->weapon, player);
    return;
  };
};

state player_fire_arrow(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  if (task->count == 0) {
    if (player->arrow == &Noarrow) {
      msg("you don't equip any arrows.");
      player_trans_state(player_action_start, player);
      return;
    };
  };
  player->sprite->surface = surface_player_attack[player->dir][animecount(task)];
  if (motion_end(task)) {
    struct _object *arrow;
    g_assert(player->arrow->count >= 1);
    if (player->arrow->count > 1) {
      arrow = arrow_add_from_quiver(player->arrow);
      player->arrow->count--;
    } else {
      arrow = player->arrow;
      object_move_from_inventory(arrow);
      equip(&Noarrow, player);
    };
    object_player_trans_state(object_player_throw, arrow, player);
    return;
  };
};

state player_pickup(struct _task *task)
{
  struct _player *player;
  struct _object *object;
  player = (struct _player *)task->pointer[0];
  object = is_object(player->coord);
  if (object != NULL) {
    if (g_list_length(player->inventory_list) < INVENTORY_MAX) {
      object_player_trans_state(object_player_pickup, object, player);
      return;
    } else {
      msg("Your inventory is full.");
      player_trans_state(player_action_start, player);
      return;
    };
  } else {
    msg("There is nothing.");
    player_trans_state(player_action_start, player);
    return;
  };
};

state player_down_stair(struct _task *task)
{
  struct _player *player;
  struct _stair *stair;
  player = (struct _player *)task->pointer[0];
  stair = is_stair(player->coord);
  if ((stair != NULL) && (stair->type == STAIR_TYPE_DOWN)) {
    game_player_trans_state(game_player_next_floor, &Game, player);
    return;
  } else {
    msg("There isn't a down-stair.");
    player_trans_state(player_action_start, player);
    return;
  };
};

state player_up_stair(struct _task *task)
{
  struct _player *player;
  struct _stair *stair;
  player = (struct _player *)task->pointer[0];
  stair = is_stair(player->coord);
  if ((stair != NULL) && (stair->type == STAIR_TYPE_UP)) {
    int i;
    struct _monster *monster;
    for (i = 0; i < 8; i++) {
      monster =is_monster(coord_dd(player->coord, ddd(i)));
      if (monster != NULL) {
	msg("%s stands in the way!",monster_name(monster));
	player_trans_state(player_action_start, player);
	return;
      };
    };
    game_player_trans_state(game_player_next_floor, &Game, player);
    return;
  } else {
    msg("There isn't a up-stair.");
    player_trans_state(player_action_start, player);
    return;
  };
};

state player_lev_up(struct _task *task)
{
  struct _player *player;
  int d;
  player = (struct _player *)task->pointer[0];
  d = (player->chp * 255) / player->mhp;
  player->lev += 1;
  player->mhp = mhp_table[player->lev];
  player->chp = (player->mhp * d) / 255;
  player->atk = atk_table[player->lev];
  msg("Welcome to experience level %d.", player->lev);
  player_trans_state(player_action_end, player);
  return;
}

state player_get_amulet_of_yendor(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->x += ((task->count % 5) - 2) * 16;
  player->sprite->surface = surface_player_attack[player->dir][animecount(task)];
  if (task->count > 64) {
    game_player_trans_state(game_player_get_amulet_of_yendor, &Game, player);
    return;
  };
}

state player_killed(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  player->sprite->surface = surface_player_attack[player->dir][animecount(task)];
  if (motion_end(task)) {
    game_player_trans_state(game_player_killed, &Game, player);
    return;
  };
}
